package jmine.tec.security.josso.gateway.servlet;

import java.io.IOException;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import jmine.tec.security.josso.JOSSOHelper;
import jmine.tec.security.josso.gateway.auth.SecurityManagerAuthScheme;
import jmine.tec.utils.debug.impl.DebugRuntimeFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.josso.Lookup;
import org.josso.auth.Credential;
import org.josso.auth.exceptions.AuthenticationFailureException;
import org.josso.auth.exceptions.SSOAuthenticationException;
import org.josso.gateway.MutableSSOContext;
import org.josso.gateway.SSOContext;
import org.josso.gateway.SSOException;
import org.josso.gateway.SSOGateway;
import org.josso.gateway.SSOWebConfiguration;
import org.josso.gateway.assertion.AuthenticationAssertion;
import org.josso.gateway.identity.SSORole;
import org.josso.gateway.identity.SSOUser;
import org.josso.gateway.session.SSOSession;
import org.josso.gateway.signon.Constants;

import bancosys.tec.security.impl.web.WebSecurityContext;
import bancosys.tec.security.impl.web.servlet.SecurityServlet;

/**
 * Servlet que lida com login utilizando JOSSO
 * 
 * @author lundberg
 * @deprecated Utilize algum LoginServlet comum, juntamente com o JOSSOGatewaySecurityManager e um JOSSOGatewayServlet para autenticar
 * aplicações partner e redirecionar.
 */
@Deprecated
public class JOSSOLoginServlet extends SecurityServlet {

    private static final Log LOG = LogFactory.getLog(JOSSOLoginServlet.class);

    private static final String DEFAULT_SUCCESS_PAGE = "defaultSuccessPage";

    private static final String LOGIN_ERROR_PAGE = "loginErrorPage";

    public static final String USERNAME_PARAM = "j_username";

    public static final String PASSWORD_PARAM = "j_password";

    private String defaultSuccessPage;

    private String loginErrorPage;

    /**
     * {@inheritDoc}
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        this.defaultSuccessPage = this.getNotNull(config.getInitParameter(DEFAULT_SUCCESS_PAGE), "/");
        this.loginErrorPage = config.getInitParameter(LOGIN_ERROR_PAGE);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        LOG.warn("Processando login com método GET.");
        this.doPost(request, response);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            this.prepareContext(request, response);
            if (SSOContext.getCurrent().getSession() != null) {
                SSOGateway g = this.getSSOGateway();
                // 1 - Recover session and create a new assertion.
                SSOSession session = SSOContext.getCurrent().getSession();
                AuthenticationAssertion authAssertion = g.assertIdentity(session.getId());

                // 2 - Restore BACK TO URL ...
                String backTo = JOSSOHelper.getBackTo(request, session, authAssertion);
                if (backTo == null) {
                    this.onLogin(request, response);
                    return;
                } else {
                    JOSSOHelper.clearSSOParameters(request);
                    response.sendRedirect(backTo);
                    return;
                }
            }
        } catch (SSOException e) {
            this.error("[Unexpected Exception] " + e.getMessage(), e);
            this.onGenericError(request, response, e);
        }
        SSOGateway gwy = this.getSSOGateway();
        String username = this.getUserName(request);
        String password = this.getPassword(request);
        try {
            Credential[] c = this.getCredentials(username, password);
            // 1 - Handle Outbound relaying by generating an assertion for the authentication request
            SSOContext ctx = SSOContext.getCurrent();
            ctx.setScheme(this.getSchemeName());
            AuthenticationAssertion authAssertion = gwy.assertIdentity(c, this.getSchemeName());

            String sessionId = authAssertion.getSSOSessionId();
            SSOSession session = gwy.findSession(sessionId);
            this.getSecurityManager().storeSubject(new WebSecurityContext(request, response), session.getSubject());
            this.storeSSOInformation(request, response, session);

            if (this.isDebug()) {
                this.debug("[login()], authentication successfull.");
            }

            // 2 - Restore BACK TO URL ...
            String backTo = JOSSOHelper.getBackTo(request, session, authAssertion);
            if (backTo == null) {

                // Return to controller, if we do not have a back-to url, add more information to the context ;)
                SSOUser user = gwy.findUserInSession(sessionId);
                SSORole[] roles = gwy.findRolesByUsername(user.getName());

                // so that pages can access this bean
                request.setAttribute(Constants.KEY_JOSSO_SESSION, session);
                request.setAttribute(Constants.KEY_JOSSO_USER, user);
                request.setAttribute(Constants.KEY_JOSSO_USER_ROLES, roles);

                this.onLogin(request, response);

                // return mapping.findForward("login-result");
            } else {
                // We're going back to the partner app.
                if (this.isDebug()) {
                    this.debug("[login()], Redirecting user to : " + backTo);
                }
                // If authentication succeds, remove al SSO session data.
                JOSSOHelper.clearSSOParameters(request);

                response.sendRedirect(backTo);
            }
        } catch (AuthenticationFailureException e) {
            this.error("[AuthenticationFailureException] " + e.getMessage(), e);
            this.onAuthenticationFailed(request, response, e);
        } catch (Throwable e) {
            this.error("[Unexpected Exception] " + e.getMessage(), e);
            this.onGenericError(request, response, e);
        }
    }

    /**
     * @param request request
     * @param response response
     * @param e e
     * @throws IOException IOException
     */
    protected void onGenericError(HttpServletRequest request, HttpServletResponse response, Throwable e) throws IOException {
        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    }

    /**
     * Ação a ser tomada quando ocorre uma falha de autenticação
     * 
     * @param request request
     * @param response response
     * @param e e
     * @throws IOException HttpServletRequest request, HttpServletResponse response
     */
    protected void onAuthenticationFailed(HttpServletRequest request, HttpServletResponse response, AuthenticationFailureException e)
            throws IOException {
        response.sendRedirect(request.getContextPath() + this.loginErrorPage);
    }

    /**
     * Ação a aser efetuada quando o login é executado com sucesso
     * 
     * @param request request
     * @param response response
     * @throws IOException IOException
     * @throws ServletException ServletException
     */
    protected void onLogin(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        this.redirectToDefaultSuccessPage(request, response);
    }

    /**
     * This method knows how to build a SSO Context based on HTTP state: request, session, etc. Some state is stored as sesion attributes
     * 
     * @see JOSSOHelper#storeSSOParameters(javax.servlet.http.HttpServletRequest)
     * @param request request
     * @param response response
     * @throws SSOException e
     */
    protected void prepareContext(HttpServletRequest request, HttpServletResponse response) throws SSOException {
        String scheme = this.getSchemeName();
        JOSSOHelper.prepareContext(request, scheme);

        if (SSOContext.getCurrent().getSession() != null) {
            SSOGateway g = this.getSSOGateway();
            // 1 - Recover session and create a new assertion.
            SSOSession session = SSOContext.getCurrent().getSession();
            g.assertIdentity(session.getId());
        }
    }

    /**
     * Stores session id
     * 
     * @param request http request
     * @param response the response
     * @param session SSO session instance
     */
    protected void storeSSOInformation(HttpServletRequest request, HttpServletResponse response, SSOSession session) {
        MutableSSOContext ctx = (MutableSSOContext) SSOContext.getCurrent();
        ctx.setCurrentSession(session);

        try {
            SSOWebConfiguration cfg = Lookup.getInstance().lookupSSOWebConfiguration();
            if (cfg.isSessionTokenOnClient()) {
                if (this.isDebug()) {
                    this.debug("Storing SSO Session ID on client");
                }
                Cookie ssoCookie =
                        JOSSOHelper.newJossoCookie(request.getContextPath(), org.josso.gateway.Constants.JOSSO_SINGLE_SIGN_ON_COOKIE + "_"
                                + ctx.getSecurityDomain().getName(), session.getId());
                response.addCookie(ssoCookie);
            } else {
                if (this.isDebug()) {
                    this.debug("Storing SSO Session ID on server");
                }
                HttpSession hsession = request.getSession();
                hsession.setAttribute(org.josso.gateway.Constants.JOSSO_SINGLE_SIGN_ON_COOKIE + "_" + ctx.getSecurityDomain().getName(),
                        session.getId());
            }
        } catch (Exception ex) {
            this.error("Error while storing SSO Information : " + ex.getMessage(), ex);
        }
    }

    /**
     * Obtem o password digitado
     * 
     * @param request {@link HttpServletRequest}
     * @return o password digitado
     */
    protected String getPassword(HttpServletRequest request) {
        return request.getParameter(PASSWORD_PARAM);
    }

    /**
     * Obtem o usuario digitado
     * 
     * @param request {@link HttpServletRequest}
     * @return o usuario digitado
     */
    protected String getUserName(HttpServletRequest request) {
        return request.getParameter(USERNAME_PARAM);
    }

    /**
     * @param request request
     * @param response response
     * @throws IOException IOException
     * @throws ServletException ServletException
     */
    private void redirectToDefaultSuccessPage(HttpServletRequest request, HttpServletResponse response) throws IOException,
            ServletException {
        response.sendRedirect(request.getContextPath() + this.defaultSuccessPage);
    }

    /**
     * @return boolean
     */
    protected boolean isDebug() {
        return DebugRuntimeFactory.ENABLED;
    }

    /**
     * @param message message
     */
    protected void debug(String message) {
        LOG.debug(message);
    }

    /**
     * @param message message
     */
    protected void error(String message) {
        LOG.error(message);
    }

    /**
     * @param message message
     * @param e e
     */
    protected void error(String message, Throwable e) {
        LOG.error(message, e);
    }

    /**
     * Gets current sso gateway.
     * 
     * @return {@link SSOGateway}
     */
    protected SSOGateway getSSOGateway() {
        final ServletContext context = this.getServletContext();
        return JOSSOHelper.getSSOGateway(context);
    }

    /**
     * Creates credentials for username and password, using configuration.
     * 
     * @param user the user name
     * @param pwd the password
     * @return array of {@link Credential}
     * @throws SSOAuthenticationException e
     */
    protected Credential[] getCredentials(String user, String pwd) throws SSOAuthenticationException {
        SSOGateway g = this.getSSOGateway();
        Credential username = g.newCredential(this.getSchemeName(), "username", user);
        Credential password = g.newCredential(this.getSchemeName(), "password", pwd);
        return new Credential[]{ username, password };

    }

    /**
     * Callback que pode ser sobrescrito
     * 
     * @return {@link String}
     */
    public String getSchemeName() {
        return SecurityManagerAuthScheme.SCHEME_NAME;
    }

    /**
     * Devolve str1 se essa for diferente de <code>null</code>, senão devolve str2.
     * 
     * @param str1 string 1
     * @param str2 string 2
     * @return str1 se essa for diferente de <code>null</code>, senão devolve str2.
     */
    @Override
    protected String getNotNull(String str1, String str2) {
        return (str1 != null) ? str1 : str2;
    }
}
